/**
 * Copyright (c) 2018, wuxie All rights reserved.
 * qq:16349023,mail:16349023@qq.com
 * 未经许可禁止任何人通过任何渠道使用、修改源代码.
 * 项目名称 : wxcloud
 *
 * @version V1.0
 */
package cn.xo68.boot.auth.client.configuration;

import cn.xo68.boot.auth.client.properties.AuthProviderProperties;
import cn.xo68.boot.auth.client.shiro.OAuth2ServerAuthorizingRealm;
import cn.xo68.boot.auth.core.common.AuthFilterEnums;
import cn.xo68.boot.auth.core.cookie.MapCookieTemplate;
import cn.xo68.boot.auth.core.domain.OAuth2AuthenticationToken;
import cn.xo68.boot.auth.core.domain.ShiroFilterMapping;
import cn.xo68.boot.auth.core.filter.Oauth2AnonFilter;
import cn.xo68.boot.auth.core.properties.OAuthResourceProperties;
import cn.xo68.boot.auth.core.shiro.HttpWebSessionManager;
import cn.xo68.boot.auth.client.shiro.OAuth2AuthenticationFilter;
import cn.xo68.boot.web.web.WebContext;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 *  shiro配置
 * @author wuxie
 * @date 2018/8/6 22:41
 *
 */
@Configuration
public class ShiroConfiguration {

    private static final Logger logger=LoggerFactory.getLogger(ShiroConfiguration.class);



    /**
     * Shiro生命周期处理器,保证实现了Shiro内部lifecycle函数的bean执行
     *
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理
     * @return
     */
    @Bean
    @DependsOn({ "lifecycleBeanPostProcessor" })
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解，会导致该方法无法映射请求，导致返回404。
         * 加入这项配置能解决这个bug
         */
        //advisorAutoProxyCreator.setUsePrefix(true);
        return advisorAutoProxyCreator;
    }
    /**
     * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }



    /**
     * 安全管理器
     * @param oAuth2ServerAuthorizingRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(OAuth2ServerAuthorizingRealm oAuth2ServerAuthorizingRealm,
                                                     CacheManager cacheManager,
                                                     OAuthResourceProperties oAuthResourceProperties,
                                                     SimpleCookie accessTokenCookie){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        //设置realm.
        securityManager.setRealm(oAuth2ServerAuthorizingRealm);

          //缓存、会话
        securityManager.setCacheManager(cacheManager);


        HttpWebSessionManager httpWebSessionManager=new HttpWebSessionManager(oAuthResourceProperties, accessTokenCookie);
        securityManager.setSessionManager(httpWebSessionManager);

        //设置了SecurityManager采用使用SecurityUtils的静态方法 获取用户等
        SecurityUtils.setSecurityManager(securityManager);

        return securityManager;
    }

    /**
     * OAuth2 服务端认证实现
     * @return
     */
    @Bean
    public OAuth2ServerAuthorizingRealm oAuth2ServerAuthorizingRealm(AuthProviderProperties authProviderProperties, OAuthResourceProperties oAuthResourceProperties){
        OAuth2ServerAuthorizingRealm realm=new OAuth2ServerAuthorizingRealm();
        realm.setCachingEnabled(false);
        realm.setAuthenticationCachingEnabled(false);
        realm.setAuthenticationCacheName("authenticationCache");
        realm.setAuthorizationCachingEnabled(false);
        realm.setAuthorizationCacheName("authorizationCache");
        //realm.setCacheManager(shiroSpringCacheManager);
        realm.setAuthenticationTokenClass(OAuth2AuthenticationToken.class);

        realm.setoAuthResourceProperties(oAuthResourceProperties);
        realm.setAuthProviderProperties(authProviderProperties);

        return realm;
    }

    /**
     * Oauth2 服务端认证过滤器
     * @Desc 无状态授权过滤器 注意不能声明为bean 否则shiro无法管理该filter生命周期，<br>该过滤器会执行其他过滤器拦截过的路径
     * @param accessTokenCookie
     * @param authProviderProperties
     * @param oAuthResourceProperties
     * @return
     */
    public AuthenticationFilter oAuth2AuthenticationFilter(MapCookieTemplate browerDataCookieTemplate,SimpleCookie accessTokenCookie , AuthProviderProperties authProviderProperties,
                                                           OAuthResourceProperties oAuthResourceProperties,
                                                           WebContext webContext){
        OAuth2AuthenticationFilter oAuth2AuthenticationFilter=new OAuth2AuthenticationFilter();
        oAuth2AuthenticationFilter.setBrowerDataCookieTemplate(browerDataCookieTemplate);
        oAuth2AuthenticationFilter.setAccessTokenCookie(accessTokenCookie);
        oAuth2AuthenticationFilter.setAuthProviderProperties(authProviderProperties);
        oAuth2AuthenticationFilter.setoAuthResourceProperties(oAuthResourceProperties);
        oAuth2AuthenticationFilter.setWebContext(webContext);
        return oAuth2AuthenticationFilter;
    }

    /**
     * shiro鉴权过滤器
     * @param securityManager
     * @param authProviderProperties
     * @param accessTokenCookie
     * @param oAuthResourceProperties
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                         AuthProviderProperties authProviderProperties,
                                                         MapCookieTemplate browerDataCookieTemplate,
                                                         SimpleCookie accessTokenCookie,
                                                         OAuthResourceProperties oAuthResourceProperties,
                                                         WebContext webContext){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();

        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 没有权限的页面
        shiroFilterFactoryBean.setUnauthorizedUrl(authProviderProperties.getUnauthorizedUrl());

        //认证过滤器注入
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        //Map<String, Filter> filterMap = new LinkedHashMap<>();

        filters.put(AuthFilterEnums.OAUTH2ANON.toString(),new Oauth2AnonFilter());
        filters.put(AuthFilterEnums.OAUTH2TOKEN.toString(),
                oAuth2AuthenticationFilter(browerDataCookieTemplate,
                        accessTokenCookie,
                        authProviderProperties,
                        oAuthResourceProperties,
                        webContext));
        //shiroFilterFactoryBean.setFilters(filterMap);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        //登录、失败、授权不足请求
        filterChainDefinitionMap.put(authProviderProperties.getUnauthorizedUrl(), AuthFilterEnums.OAUTH2ANON.toString());

        // 静态资源、错误信息
        filterChainDefinitionMap.put("/favicon.ico", AuthFilterEnums.OAUTH2ANON.toString());
        filterChainDefinitionMap.put("/public/**", AuthFilterEnums.OAUTH2ANON.toString());
        filterChainDefinitionMap.put("/static/**", AuthFilterEnums.OAUTH2ANON.toString());
        filterChainDefinitionMap.put("/webjars/**", AuthFilterEnums.OAUTH2ANON.toString());
        filterChainDefinitionMap.put("/error/**", AuthFilterEnums.OAUTH2ANON.toString());

        if(oAuthResourceProperties.getFilterMappings()!=null){
            for(ShiroFilterMapping shiroFilterMapping: oAuthResourceProperties.getFilterMappings()){
                filterChainDefinitionMap.put(shiroFilterMapping.getPath(), shiroFilterMapping.getFilterPattern());
            }
        }
        filterChainDefinitionMap.put("/**", AuthFilterEnums.OAUTH2TOKEN.getName());

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }


    /**
     * 静态注入，相当于调用SecurityUtils.setSecurityManager(securityManager)
     * @param securityManager
     * @return
     */
    @Bean
    public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager){
        MethodInvokingFactoryBean methodInvokingFactoryBean=new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setArguments(securityManager);
        methodInvokingFactoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        try {
            methodInvokingFactoryBean.afterPropertiesSet();
        } catch (Exception e) {
            //e.printStackTrace();
            logger.error("异步方法注入异常",e);
        }
        return methodInvokingFactoryBean;
    }
}
